programming4us
           
 
 
SQL Server

SQL server 2012 : T-SQL Enhancements - Table-Valued Parameters (part 2)

- Free product key for windows 10
- Free Product Key for Microsoft office 365
- Malwarebytes Premium 3.7.1 Serial Keys (LifeTime) 2019
7/10/2013 7:24:51 PM

Passing TVPs Using ADO.NET

We’ll conclude our discussion of TVPs with the new SqlDbType.Structured enumeration in ADO.NET. You will learn how easy it is to marshal multiple rows of data from your client application to SQL Server, without requiring multiple roundtrips or implementing special logic on the server for processing the data.

Simply prepare an ordinary SqlCommand object , set its CommandType property to CommandType.StoredProcedure, and populate its Parameters collection with SqlParameter objects. These are the routine steps for setting up a call to any stored procedure. Then, to mark a SqlParameter as a TVP, set its SqlDbType property to SqlDbType.Structured. This lets you specify any DataTable, DbDataReader, or IEnumerable<SqlDataRecord> object as the parameter value to be passed to the stored procedure in a single call to the server.

In Example 6, a new customer order is stored in separate Order and OrderDetail DataTable objects within a DataSet. The two tables are passed to the SQL Server stored procedure you saw earlier (Example 2 in the section Submitting Orders), which accepts them as TVPs for insertion into the Order and OrderDetail database tables. 

Example 6. Passing DataTable objects as TVPs to a SQL Server stored procedure from ADO.NET.

// Assumes conn is an open SqlConnection object and ds is
// a DataSet with an Order and OrderDetails table
using(conn)
{
    // Create the command object to call the stored procedure
    SqlCommand cmd = new SqlCommand("uspInsertNewOrder", conn);
    cmd.CommandType = CommandType.StoredProcedure;

    // Create the parameter for passing the Order TVP
    SqlParameter headerParam = cmd.Parameters.AddWithValue
     ("@OrderHeaderTvp", ds.Tables["OrderHeader"]);

    // Create the parameter for passing the OrderDetails TVP
    SqlParameter detailsParam = cmd.Parameters.AddWithValue
     ("@OrderDetailsTvp", ds.Tables["OrderDetail"]);

    // Set the SqlDbType of the parameters to Structured
    headerParam.SqlDbType = SqlDbType.Structured;
    detailsParam.SqlDbType = SqlDbType.Structured;

    // Execute the stored procedure
    cmd.ExecuteNonQuery();
}

This code calls a SQL Server stored procedure and passes to it an order header and the complete set of order details in a single roundtrip. Remarkably, it’s just as simple as that. You just need to ensure that the schema of the DataTable objects match the corresponding TVP-table-type schema.

You can also send a set of rows directly to a parameterized SQL statement without creating a stored procedure. Because the SQL statement is dynamically constructed on the client, there is no stored procedure signature that specifies the name of the user-defined table type for the TVP. Therefore, you need to tell ADO.NET what the type is by setting the TypeName property to the name of the table type defined on the server. For example, the code in Example 7 passes a DataTable to a parameterized SQL statement.

Example 7. Passing TVPs to a parameterized SQL statement from ADO.NET.

// Define the INSERT INTO...SELECT statement to insert into Categories
const string TSqlStatement = @"
 INSERT INTO Categories (CategoryID, CategoryName)
  SELECT nc.CategoryID, nc.CategoryName
  FROM @NewCategoriesTvp AS nc";

// Assumes conn is an open SqlConnection object and ds is
// a DataSet with a Category table
using(conn)
{
    // Set up the command object for the statement
    SqlCommand cmd = new SqlCommand(TSqlStatement, conn);

    // Add a TVP specifying the DataTable as the parameter value
    SqlParameter catParam = cmd.Parameters.AddWithValue
     ("@NewCategoriesTvp", ds.Tables["Category"]);

    catParam.SqlDbType = SqlDbType.Structured;
    catParam.TypeName = "dbo.CategoriesUdt";

    // Execute the command
    cmd.ExecuteNonQuery();
}

Setting the TypeName property to dbo.CategoriesUdt in this code means that you have a user-defined table type by that name on the server, created using the CREATE TYPE…AS TABLE statement that defines the CategoryID and CategoryName columns.

You can also use any object derived from DbDataReader (that is, a connected data source) to stream rows of data to a TVP. The example shown in Example 8 calls an Oracle stored procedure to select data from an Oracle database into a connected OracleDataReader. The reader object gets passed as a single table-valued input parameter to a SQL Server stored procedure, which can then use the Oracle data in the reader as the source for adding new rows into the Category table in the SQL Server database (for this code to work, you must have the ADO.NET Provider for Oracle installed and a project reference set to System.Data.OracleClient, as well as access to an Oracle database server).

Example 8. Passing a connected OracleDataReader source as a TVP to SQL Server.

// Set up command object to select from Oracle
OracleCommand selCmd = new OracleCommand
 ("SELECT CategoryID, CategoryName FROM Categories;", oracleConn);

// Execute the command and return the results in a connected
// reader that will automatically close the connection when done
OracleDataReader rdr = selCmd.ExecuteReader(CommandBehavior.CloseConnection);

// Set up command object to insert to SQL Server
SqlCommand insCmd = new SqlCommand("uspInsertCategories", connection);

insCmd.CommandType = CommandType.StoredProcedure;

// Add a TVP specifying the reader as the parameter value
SqlParameter catParam = cmd.Parameters.AddWithValue("@NewCategoriesTvp", rdr);

catParam.SqlDbType = SqlDbType.Structured;

// Execute the stored procedure
insertCommand.ExecuteNonQuery();

Passing Collections to TVPs Using Custom Iterators

What if you’re working with collections populated with business objects rather than DataTable objects populated with DataRow objects? You might not think at first that business objects and TVPs could work together, but the fact is that they can—and quite gracefully, too. All you need to do is implement the IEnumerable<SqlDataRecord> interface in your collection class. This interface requires your collection class to supply a custom iterator method named GetEnumerator, which ADO.NET will call for each object contained in the collection when you invoke ExecuteNonQuery.

Let’s demonstrate with the same order entry example as Example 6, only now you’ll use ordinary business object collections (rather than DataTable and DataRow objects) as the source for the TVPs. You have OrderHeader and OrderDetail classes with properties to match the corresponding TVP-table-type schemas, as shown in Example 9. Of course, a real implementation could include much more than this simple data transfer object, such as encapsulated business logic. 

Example 2-9. Defining classes for passing business objects as TVPs.

public class OrderHeader
{
  public int OrderId { get; set; }
  public int CustomerId { get; set; }
  public DateTime OrderedAt { get; set; }
}

public class OrderDetail
{
  public int OrderId { get; set; }
  public int LineNumber { get; set; }
  public int ProductId { get; set; }
  public int Quantity { get; set; }
  public decimal Price { get; set; }
}

Ordinarily, working with List<OrderHeader> and List<OrderDetail> objects might be a suitable option for containing collections of OrderHeader and OrderDetail objects in your application. But in these collections they won’t suffice on their own as input values for TVPs because List<T> does not implement IEnumerable<SqlDataRecord>. The solution is to create OrderHeaderCollection and OrderDetailCollection classes that inherit List<OrderHeader> and List<OrderDetail> respectively, and then implement IEnumerable<SqlDataRecord> in order to “TVP-enable” them as shown in Example 10.

Example 10. Defining collection classes with custom iterators for passing business objects as TVPs.

public class OrderHeaderCollection : List<OrderHeader>, IEnumerable<SqlDataRecord>
{
  IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
  {
    var sdr = new SqlDataRecord(
     new SqlMetaData("OrderId", SqlDbType.Int),
     new SqlMetaData("CustomerId", SqlDbType.Int),
     new SqlMetaData("OrderedAt", SqlDbType.Date));

    foreach (OrderHeader oh in this)
    {
      sdr.SetInt32(0, oh.OrderId);
      sdr.SetInt32(1, oh.CustomerId);
      sdr.SetDateTime(2, oh.OrderedAt);

      yield return sdr;
    }
  }
}

public class OrderDetailCollection : List<OrderDetail>, IEnumerable<SqlDataRecord>
{
  IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
  {
    var sdr = new SqlDataRecord(
     new SqlMetaData("OrderId", SqlDbType.Int),
     new SqlMetaData("LineNumber", SqlDbType.Int),
     new SqlMetaData("ProductId", SqlDbType.Int),
     new SqlMetaData("Quantity", SqlDbType.Int),
     new SqlMetaData("Price", SqlDbType.Money));

    foreach (OrderDetail od in this)
    {
      sdr.SetInt32(0, od.OrderId);
      sdr.SetInt32(1, od.LineNumber);
      sdr.SetInt32(2, od.ProductId);
      sdr.SetInt32(3, od.Quantity);
      sdr.SetDecimal(4, od.Price);

      yield return sdr;
    }
  }
}

We’ll only explain the OrderHeaderCollection class; you’ll be able to infer how the OrderDetailCollection class—or any of your own collection classes—implements the custom iterator needed to support TVPs.

First, again, it inherits List<OrderHeader>, so an OrderHeaderCollection object is everything that a List<OrderHeader> object is. This means implicitly, by the way, that it also implements IEnumerable<OrderHeader>, which is what makes any sequence “foreach-able” or “LINQ-able.” But to the heart of our discussion, it explicitly implements IEnumerable<SqlDataRecord>, which means it has a custom iterator method for ADO.NET to consume when an instance of this collection class is assigned to a SqlDbType.Structured parameter for piping over to SQL Server with a TVP.

Every enumerable class requires a matching enumerator method, so not surprisingly implementing IEnumerable<SqlDataRecord> requires providing a GetEnumerator method that returns an IEnumerator<SqlDataRecord>. This method first initializes a new SqlDataRecord object with a schema that matches the table-type schema that the TVPs are declared as. It then enters a loop that iterates all the elements in the collection (possible because List<OrderHeader> implicitly implements IEnumerable<OrderHeader>). On the first iteration, it sets the column property values of the SqlDataRecord object to the property values of the first OrderHeader element, and then issues the magic yield return statement. By definition, any method (like this one) which returns IEnumerator<T> and has a yield return statement in it is a custom iterator method; it is expected to return a sequence of T objects until the method execution path completes (in this case, when the foreach loop finishes).

The crux of this is that you are never calling this method directly. Instead, when you invoke ExecuteNonQuery to run a stored procedure with a SqlDbType.Structured parameter (that is, a TVP), ADO.NET expects the collection passed for the parameter value to implement IEnumerable<SqlDataRecord> so that IEnumerable<SqlDataRecord>.GetEnumerator can be called internally to fetch each new record for piping over to the server. When the first element is fetched from the collection, GetEnumerator is entered, the SqlDataRecord is initialized, and is then populated with values using the SetInt32 and SetDateTime methods (there’s a SetXXX method for each data type). That SqlDataRecord “row” is then pushed into the pipeline to the server by yield return. When the next element is fetched from the collection, the GetEnumerator method resumes from the point that it yield returned the previous element, rather than entering GetEnumerator again from the top. This means the SqlDataRecord gets initialized with schema information only once, while its population with one element after another is orchestrated by the controlling ADO.NET code for ExecuteNonQuery that actually ships one SqlDataRecord after another to the server.

The actual code to call the stored procedure is 100 percent identical to the code in Example 6 that uses a DataTable rather than a collection. Substituting a collection for a DataTable object requires no code changes and works flawlessly, provided the collection implements IEnumerator<SqlDataRecord>. This mean the collection has a GetEnumerator method that feeds each object instance to a SqlDataRecord and maps each object property to a column defined by the user-defined table type that the TVP is declared as.

TVP Limitations

TVPs have several noteworthy limitations. First and foremost, once TVPs are initially populated and passed, they are read-only structures. The READONLY keyword must be applied to TVPs in the signatures of your stored procedures, or they will not compile. Similarly, the OUTPUT keyword cannot be used. You cannot update the column values in the rows of a TVP, and you cannot insert or delete rows. If you must modify the data in a TVP, you need to implement a workaround, such as inserting data from the TVP into a temporary table or into a table variable to which you can then apply changes.

There is no ALTER TABLE…AS TYPE statement that supports changing the schema of a TVP table type. Instead, you must first drop all stored procedures and UDFs that reference the type before you can drop the type, re-create it with a new schema, and then re-create the stored procedures. Indexing is limited as well, with support only for PRIMARY KEY and UNIQUE constraints. Also, statistics on TVPs are not maintained by SQL Server.

It’s also important to note that the Entity Framework does not support TVPs. However, you can easily send any collection of Entity Framework entities to a stored procedure that accepts TVPs by coding a custom iterator just like the one you created in the section Passing Collections to TVPs Using Custom Iterators.

Other -----------------
- SQL Server 2008 R2 : Database Files and Filegroups (part 2)
- SQL Server 2008 R2 : Database Files and Filegroups (part 1)
- Installing SQL Server 2012 : The Installation Process (part 4) - Post Installation Tasks
- Installing SQL Server 2012 : The Installation Process (part 3) - Installing SQL Server 2012 Through the Command Line, Installing SQL Server 2012 Through PowerShell
- Installing SQL Server 2012 : The Installation Process (part 2) - Installing SQL Server 2012 Through the Installation Center
- Installing SQL Server 2012 : The Installation Process (part 1) - SQL Server 2012 Installation Center
- Installing SQL Server 2012 : Preparing the Server, Selecting the Edition
- SQL Server 2012 : SQL Server Architecture - SQL SERVER’S EXECUTION MODEL AND THE SQLOS
- SQL Server 2012 : SQL Server Architecture - THE LIFE CYCLE OF A QUERY (part 3) - A Simple Update Query
- SQL Server 2012 : SQL Server Architecture - THE LIFE CYCLE OF A QUERY (part 2) - Plan Cache
- SQL Server 2012 : SQL Server Architecture - THE LIFE CYCLE OF A QUERY (part 1)
- Protecting SQL Server Data : CELL-LEVEL ENCRYPTION - Views and Stored Procedures (part 2) - Creating the Stored Procedures
- Protecting SQL Server Data : CELL-LEVEL ENCRYPTION - Views and Stored Procedures (part 1) - Creating the View
- Protecting SQL Server Data : Implementing Cell-Level Encryption
- Protecting SQL Server Data : Preparing for Cell-Level Encryption
- Microsoft SQL Server 2008 R2 : Monitoring Replication (part 2) - New and Improved Peer-to-Peer Replication
- Microsoft SQL Server 2008 R2 : Monitoring Replication (part 1) - Replication Monitoring SQL Statements
- Microsoft SQL Server 2008 R2 : Scripting Replication
- Processing and Storing Data in SQL Server 2005 : Data Migration from One Data Store to Another Data Store
- Processing and Storing Data in SQL Server 2005 : Implementing the Record Failure Code
 
 
 
Top 10
 
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
programming4us programming4us